#include <QTextStream>

#include "gcad_multilayer.h"
#include "mv.h"

#include <models/model_mesh.h>

namespace formats { namespace gcad_multilayer {

  namespace { // Statics

    QDir GetGCADRoot(const QString & _FileName)
    {
      QDir Dir(_FileName);
      Dir.cd("../../../..");
      return Dir;
    }

    void ReadProperties(const QString & _FileName, QObject * _pObject)
    {
      QFile File(_FileName);

      if (!File.open(QIODevice::ReadOnly | QIODevice::Text))
        return;

      QTextStream Stream(&File);
      QRegExp RegExp("(?:\\s*)(\\S+)(?:\\s*=\\s*)(\\S+)(?:.*)");

      while (!Stream.atEnd())
      {
        const QString Line = Stream.readLine();

        if (RegExp.indexIn(Line) != -1)
          _pObject->setProperty(RegExp.cap(1).toAscii(), RegExp.cap(2));
      }
    }

    int ReadMaterialData(const QDir & GCADRoot_, const QString & MaterialName_, QStringList & ComponentNames_, QStringList & MeshFiles_)
    {
      ComponentNames_.clear();
      MeshFiles_.clear();

      const QString materialFileName = GCADRoot_.absoluteFilePath("dat/mat/" + MaterialName_ + "/" + MaterialName_ + ".sld");

      QObject p;
      ReadProperties(materialFileName, &p);

      const QString HPPSolverName    = p.property("HPPSolverName").toString();
      const QString HPPTaskName      = p.property("HPPTaskName").toString();

      QString componentQtyString = p.property("ComponentsCount").toString();
      if (componentQtyString != "@COMPCOUNT@")
      {
        const int componentQty = componentQtyString.toInt();

        for (int componentIndex = 1; componentIndex <= componentQty; ++componentIndex)
        {
          const QString indexString = QString("%1").arg(componentIndex);
          const QString componentKey = QString("Component") + indexString;

          ComponentNames_.append(p.property(componentKey.toAscii()).toString());
          MeshFiles_.append(GCADRoot_.absoluteFilePath("out/" + HPPSolverName + "/" + HPPTaskName + "/" + HPPTaskName + "00.mvsctp" + indexString));
        }
      }
      else
      {
        for (int componentIndex = 1; ; ++componentIndex)
        {
          const QString indexString   = QString("%1").arg(componentIndex);
          const QString componentFile = GCADRoot_.absoluteFilePath("out/" + HPPSolverName + "/" + HPPTaskName + "/" + HPPTaskName + "00.mvsctp" + indexString);
          QFile file(componentFile);

          if (!file.exists())
            break;

          ComponentNames_.append(indexString);
          MeshFiles_.append(componentFile);
        }
      }

      return ComponentNames_.size();
    }

    void AppendField(
        QVector<models::Mesh::Scalar_field::SP> & _Fields,
        const QString                                 & _FieldName,
        const QVector<qreal>                          & _FieldValues
      )
    {
      models::Mesh::Scalar_field::SP pField(
          new models::Mesh::Scalar_field(
              _FieldName,
              _FieldValues
            )
        );
      _Fields.append(pField);
    }

    bool ReadMultilevelStructure(const QDir & _GCADRoot, QObject *pModel, const QString & MaterialName_)
    {
      qDebug("[gcad] Reading material structure: %s", MaterialName_.toStdString().c_str());
      using namespace models;
      formats::mv::Format FormatMV;

      QStringList componentNames;
      QStringList componentFiles;

      const int componentQty = ReadMaterialData(_GCADRoot, MaterialName_, componentNames, componentFiles);
      
      for (int componentIndex = 0; componentIndex < componentQty; ++componentIndex)
      {
        const QString componentName = componentNames[componentIndex];
        const QString componentFile = componentFiles[componentIndex];

        qDebug("[gcad] Component: %s; File: %s", componentName.toStdString().c_str(), componentFile.toStdString().c_str());
        Mesh * pSubModel = qobject_cast<Mesh *>(FormatMV.Restore(componentFile));

        if (!pSubModel)
        {
          // TODO: cleanup
          return false;
        }

        pSubModel->setParent(pModel);
        pSubModel->setObjectName(pModel->objectName() + "." + componentName);

        { // Append stress parameters
          Mesh::Frame::SP pFrame = pSubModel->get_frame();
          QVector<Mesh::Scalar_field::SP> Fields = pFrame->get_element_field(IMesh::element_triangle);
          QVector<qreal> values;
          values.resize(Fields[0]->get_values().size());
          Mesh::Geometry::SP pGeometry = pFrame->get_geometry();

          AppendField(Fields, "XXStressParameterName", values);
          AppendField(Fields, "YYStressParameterName", values);
          AppendField(Fields, "ZZStressParameterName", values);
           
          AppendField(Fields, "XYStressParameterName", values);
          AppendField(Fields, "XZStressParameterName", values);
          AppendField(Fields, "YZStressParameterName", values);
          pFrame->set_element_field(IMesh::element_triangle, Fields);
          pSubModel->set_current_frame(0);
        }

        foreach (const QString & componentName, componentNames)
          if (!ReadMultilevelStructure(_GCADRoot, pSubModel, componentName))
          {
            pSubModel->deleteLater();
            return false;
          }
      }

      return true;
    }

    void TranslateField(
        models::Mesh::Frame::SP _pFrame,
        const QString & _VertexFieldName,
        const QString & _ElementFieldName
      )
    {
      using namespace models;
      
      Mesh::Scalar_field::SP pVertexField;
      Mesh::Scalar_field::SP pElementField;
      
      Mesh::Geometry::SP pGeometry = _pFrame->get_geometry();
      
      for (int i = 0; i < _pFrame->get_param_qty(); i++)
        if (_pFrame->get_param(i)->get_name() == _VertexFieldName)
        {
          pVertexField = _pFrame->get_param(i);
          break;
        }
      
      for (int i = 0; i < _pFrame->get_element_param_qty(IMesh::element_triangle); i++)
        if (_pFrame->get_element_param(IMesh::element_triangle, i)->get_name() == _ElementFieldName)
        {
          pElementField = _pFrame->get_element_param(IMesh::element_triangle, i);
          break;
        }
      
      IMesh::t_container_element nodes = pGeometry->get_nodes(IMesh::element_triangle);
      const size_t triangleCount = nodes.size() / 3;
      
      const QVector<qreal> & vertexValues(pVertexField->get_values());
      QVector<qreal>       & elementValues(pElementField->get_values());
      for (int i = 0; i < triangleCount; ++i)
      {
        qreal val = 0.0;
        
        for (int j = 0; j < 3; ++j)
          val += vertexValues[nodes[i * 3 + j]];
        val /= 3;
        
        for (int j = 0; j < 3; ++j)
          elementValues[i * 3 + j] = val;
      }
      pElementField->recalc();
    }
  } // namespace {

  //
  // gcad::ModelFormat
  //

  bool Format::IsStorable() const
  {
    return false;
  }

  bool Format::IsRestorable() const
  {
    return true;
  }

  void Format::Store(QObject *, const QString &) const
  {
    assert(!"mv_multilayer storing not supported");
  }

  QObject *Format::Restore(const QString & _FileName) const
  {
    using namespace models;

    QDir GCADRoot = GetGCADRoot(_FileName);

    formats::mv::Format formatMV;

    Mesh * pRootModel = qobject_cast<models::Mesh *>(formatMV.Restore(_FileName));
    const QString materialName = pRootModel->property("MaterialName").toString();
    pRootModel->setObjectName("root." + materialName);
    
    if (pRootModel->property("TranslateSigma").toBool())
    { // Hack, field translation
      Mesh::Frame::SP pFrame = pRootModel->get_frame();
      QVector<Mesh::Scalar_field::SP> Fields = pFrame->get_element_field(IMesh::element_triangle);

      QVector<qreal> values;
      values.resize(Fields[0]->get_values().size());
      Mesh::Geometry::SP pGeometry = pFrame->get_geometry();

      AppendField(Fields, "XXStressParameterName", values);
      AppendField(Fields, "YYStressParameterName", values);
      AppendField(Fields, "ZZStressParameterName", values);
       
      AppendField(Fields, "XYStressParameterName", values);
      AppendField(Fields, "XZStressParameterName", values);
      AppendField(Fields, "YZStressParameterName", values);
      pFrame->set_element_field(IMesh::element_triangle, Fields);
       
      TranslateField(pFrame, "Sigma_X",  "XXStressParameterName");
      TranslateField(pFrame, "Sigma_Y",  "YYStressParameterName");
      TranslateField(pFrame, "Sigma_XY", "XYStressParameterName");
      TranslateField(pFrame, "Sigma_XZ", "XZStressParameterName");
      TranslateField(pFrame, "Sigma_YZ", "YZStressParameterName");

      pRootModel->set_current_frame(0);
    }

    if (!materialName.isEmpty() &&
        !ReadMultilevelStructure(GCADRoot, pRootModel, materialName))
    {
      pRootModel->deleteLater();
      pRootModel = 0;
    }

    return pRootModel;
  }

  QString Format::GetDisplayName() const
  {
    return QObject::tr("GCAD MultiLayer format");
  }

  QString Format::GetFilter() const
  {
    return QObject::tr("GCAD MultiLayer format (*.*)");
  }
} } // namespace formats { namespace gcad_multilayer {